#ifndef LOG_H
#define LOG_H

//  功能: 格式化字符串
//  作者: mmc
//  日期: 2017/10/18

#include <tuple>
#include <string>
#include <algorithm>
#include <chrono>
#include <iomanip>

template <class T>
inline void ToString(std::string &ret, T &&val)
{
        ret.append(std::to_string(std::forward<T>(val)));
}

inline void ToString(std::string &ret, const std::string &val)
{
        ret.append(val);
}

inline void ToString(std::string &ret, const char *val)
{
        ret.append(val);
}

inline void ToString(std::string &ret, char *val)
{
        ret.append(val);
}

template <int N>
struct SFormatN
{
        static std::string Format(const char *fmt)
        {
                return fmt;
        }
};

template <>
struct SFormatN<0>
{
        template <class... ARGS>
        static std::string Format(const char *fmt, const std::tuple<ARGS...> &)
        {
                return fmt;
        }
};

template <class... ARGS>
std::string SFormat(const char *fmt, const ARGS &...args)
{
        const auto tuple = std::forward_as_tuple(args...);
        return SFormatN<sizeof...(args)>::Format(fmt, tuple);
}

#define FMT_N(idx)                                  \
        case idx:                                   \
                ToString(ret, std::get<idx>(args)); \
                break;

#define FMT_PARSE(N, ...)                                                                   \
        template <>                                                                         \
        struct SFormatN<N>                                                                  \
        {                                                                                   \
                template <class... ARGS>                                                    \
                static std::string Format(const char *fmt, const std::tuple<ARGS...> &args) \
                {                                                                           \
                        std::string ret;                                                    \
                        while (*fmt != '\0')                                                \
                        {                                                                   \
                                auto idx = -1;                                              \
                                if (*fmt == '{')                                            \
                                {                                                           \
                                        idx = 0;                                            \
                                        ++fmt;                                              \
                                        while (*fmt >= '0' && *fmt <= '9')                  \
                                        {                                                   \
                                                idx *= 10;                                  \
                                                idx += (int)(*fmt++ - '0');                 \
                                        }                                                   \
                                        if (*fmt != '}')                                    \
                                                idx = -1;                                   \
                                        else                                                \
                                                ++fmt;                                      \
                                }                                                           \
                                switch (idx)                                                \
                                {                                                           \
                                        __VA_ARGS__ default : ret.append(1, *fmt++);        \
                                        break;                                              \
                                }                                                           \
                        }                                                                   \
                        return ret;                                                         \
                }                                                                           \
        };

FMT_PARSE(1, FMT_N(0))
FMT_PARSE(2, FMT_N(0) FMT_N(1))
FMT_PARSE(3, FMT_N(0) FMT_N(1) FMT_N(2))
FMT_PARSE(4, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3))
FMT_PARSE(5, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4))
FMT_PARSE(6, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5))
FMT_PARSE(7, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6))
FMT_PARSE(8, FMT_N(0) FMT_N(1) FMT_N(2) FMT_N(3) FMT_N(4) FMT_N(5) FMT_N(6) FMT_N(7))

#if !(defined QTDEBUG_LOG)
#define QTDEBUG_LOG 0
#define STDDEBUG_LOG 1
#endif

#include <sstream>
#if (STDDEBUG_LOG == 1)
#include <iostream>
#endif

#if (QTDEBUG_LOG == 1)
#include <QDebug>
#endif

namespace gmtlib
{

        /**
         * @brief log 工具类, example: Log::d("TAG", "hello world, {0}, {0}, {1}, {2}, {3}", 123, 1.23, "123", std::string("123"));
         *              {0} is first parameter, {1} is second parameter, etc..
         *
         * @author yuchuanxin
         * @date 2019-11-07
         */
        class Log
        {
        public:
                typedef enum LogLevel
                {
                        LogLevelNone = 0,
                        LogLevelError = 0x1,
                        LogLevelWarning = 0x3,
                        LogLevelDebug = 0x7
                } LogLevel;

                Log() = delete;

                template <class... ARGS>
                static void d(const std::string &tag, const std::string &fmt, const ARGS &...args)
                {
                        build("[D]", tag, fmt, args...);
                }

                template <class... ARGS>
                static void w(const std::string &tag, const std::string &fmt, const ARGS &...args)
                {
                        build("[W]", tag, fmt, args...);
                }

                template <class... ARGS>
                static void e(const std::string &tag, const std::string &fmt, const ARGS &...args)
                {
                        build("[E]", tag, fmt, args...);
                }

                static void setLogLevel(LogLevel level)
                {
                        logLevel = level;
                }

        private:

                template <class... ARGS>
                static void build(const std::string &type, const std::string &tag, const std::string &fmt, const ARGS &...args)
                {                        
                        std::stringstream ss;
                        time_t timep;
                        time(&timep);
                        struct tm  *timeinfo = std::localtime(&timep);
                        ss << type;
                        ss << "[";
                        ss << std::put_time(timeinfo, "%Y-%m-%d %H:%M:%S");
                        ss << "][" << tag << "]: " << SFormat(fmt.c_str(), args...) << std::endl;
#if (QTDEBUG_LOG == 1)
                        qDebug() << ss.str().c_str();
#endif

#if (STDDEBUG_LOG == 1)
                        std::cout << ss.str();
#endif
                }

                static LogLevel logLevel;
        };

}
#endif // LOG_H
